﻿using System;
using System.Windows.Forms;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using Resource = SlimDX.Direct3D11.Resource;
using Device = SlimDX.Direct3D11.Device;

namespace HDM.NexusEngine.Graphics
{
    /// <summary>
    /// 
    /// </summary>
    public class GraphicsDeviceManager : IDisposable
    {
        private Device _device;
        private Viewport _viewport;
        private SwapChain _swapChain;

        public RenderTargetView RenderTarget { get; set; }
        public SwapChain SwapChain { get { return _swapChain; } }
        public DeviceContext Context { get; set; }

        public GraphicsDeviceManager(Control form)
        {
            CreateDeviceAndSwapChain(form);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="form"></param>
        private void CreateDeviceAndSwapChain(Control form)
        {
            var description = new SwapChainDescription
            {
                // The number of buffers to create, including the front buffer.
                // For a windowed device, the desktop is an implicit front buffer and doesn’t need to be taken into account.
                // At least one back buffer should be created in order to enable flipping, as opposed to copying the video memory each frame. 
                BufferCount = 1,
                // The expected usage of the back buffers.
                // Since we will be rendering to this, we will specify Usage.RenderTargetOutput.
                // The only other valid member for a swap chain is ShaderInput, which lets you use the back buffer as an input for postprocessing effects. 
                Usage = Usage.RenderTargetOutput,
                // Designates the handle of the window to which the swap chain will be attached.
                // This member can be used to tie the form to the swap chain via the form’s Handle property. 
                OutputHandle = form.Handle,
                // Specifies whether to operate in fullscreen or windowed mode.
                // The DirectX SDK recommends that you always create the swap chain in windowed mode
                // and then manually transition to fullscreen in order to avoid mismatching the size of the output with the swap chain buffer,
                // which can reduce performance. 
                IsWindowed = true,
                // Allows you to specify the desired display mode of the swap chain.
                // When starting in windowed mode, you typically set the size of the window as the resolution and pick a default refresh rate of 60 Hz
                // and one of the standard back buffer formats.
                // Passing zero for the width and height will automatically size the swap chain to the attached window. 
                ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
                // Allows you to specify multisampling parameters.
                // The Count parameter lets you pick the number of samples per pixel, and the Quality parameter picks an antialiasing quality level.
                SampleDescription = new SampleDescription(1, 0),
                // Specifies additional flags that affect the behavior of the swap chain.
                // Of interest is the AllowModeSwitch flag, which tells DXGI that the swap chain is allowed to adjust the display mode
                // to a more optimal one if necessary. 
                Flags = SwapChainFlags.AllowModeSwitch,
                // This determines how data in the back buffers are moved to the front buffer.
                // Discard lets DXGI dump all back buffer data after it has been used, which allows it to do things in the most efficient manner.
                // Sequential preserves the contents of the back buffer, which may be necessary if you wish to read its contents.
                SwapEffect = SwapEffect.Discard
            };

            
            Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, description, out _device, out _swapChain);

            // create a view of our render target, which is the backbuffer of the swap chain we just created
            using (var resource = Resource.FromSwapChain<Texture2D>(SwapChain, 0))
            {
                RenderTarget = new RenderTargetView(_device, resource);
            }

            // setting a viewport is required if you want to actually see anything
            Context = _device.ImmediateContext;
            _viewport = new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
            Context.OutputMerger.SetTargets(RenderTarget);
            Context.Rasterizer.SetViewports(_viewport);

            // prevent DXGI handling of alt+enter, which doesn't work properly with Winforms
            using (var factory = SwapChain.GetParent<Factory>())
            {
                factory.SetWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAltEnter);
            }

            // handle alt+enter ourselves
            form.KeyDown += (o, e) =>
            {
                if (e.Alt && e.KeyCode == Keys.Enter)
                {
                    SwapChain.IsFullScreen = !SwapChain.IsFullScreen;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            RenderTarget.Dispose();
            SwapChain.Dispose();
            _device.Dispose();
        }
    }
}